/**
 * the core synth, please refer to the chart for detail
 * It depends on jweb.util.WaveShaper @see waveshaper.js
 * @author maoanapex88@163.com Mao Anping
 * 
 */
jweb.util.Voice = function(note, velocity, synth, autoStop, startTime, stopTime){
	//we used percentage of the note duration to perform ADSR
	var durationSec = stopTime - startTime;
	this._duration = durationSec;
	this.note = note;
	this.synth = synth;
	this.ctx = synth.audioContext;
	this._autostop = (autoStop?autoStop:false);
	this._start = (startTime?startTime:0);
	this._stop = (stopTime?stopTime:0);
	
	var audioContext=synth.audioContext;
	
	this.originalFrequency = synth.frequencyFromNoteNumber( note );

	// create osc 1
	this.osc1 = audioContext.createOscillator();
	this.updateOsc1Frequency();
	this.osc1.type = synth.waveforms[synth.currentOsc1Waveform];

	this.osc1Gain = audioContext.createGain();
	this.osc1Gain.gain.value = 0.005 * synth.currentOsc1Mix;
	//	this.gain.gain.value = 0.05 + (0.33 * velocity);
	this.osc1.connect( this.osc1Gain );

	// create osc 2
	this.osc2 = audioContext.createOscillator();
	this.updateOsc2Frequency();
	this.osc2.type = synth.waveforms[synth.currentOsc2Waveform];

	this.osc2Gain = audioContext.createGain();
	this.osc2Gain.gain.value = 0.005 * synth.currentOsc2Mix;
	this.osc2.connect( this.osc2Gain );

	// create modulator osc1
	this.modOsc1 = audioContext.createOscillator();
	this.modOsc1.type = synth.waveforms[synth.currentModWaveform1];
	this.modOsc1.frequency.value = synth.currentModFrequency1 * synth.modOscFreqMultiplier;

	//modgain1 to osc1
	this.modOsc1Gain = audioContext.createGain();
	this.modOsc1.connect( this.modOsc1Gain );
	this.modOsc1Gain.gain.value = synth.currentModOsc1/10;
	this.modOsc1Gain.connect( this.osc1.frequency );	// tremolo
	
	// create modulator osc2
	this.modOsc2 = audioContext.createOscillator();
	this.modOsc2.type = synth.waveforms[synth.currentModWaveform1];
	this.modOsc2.frequency.value = synth.currentModFrequency2 * synth.modOscFreqMultiplier;
	
	//modgain2 to osc2
	this.modOsc2Gain = audioContext.createGain();
	this.modOsc2.connect( this.modOsc2Gain );
	this.modOsc2Gain.gain.value = synth.currentModOsc2/10;
	this.modOsc2Gain.connect( this.osc2.frequency );	// tremolo

	// create the LP filter1
	this.filter11 = audioContext.createBiquadFilter();
	this.filter11.type = "lowpass";
	this.filter11.Q.value = synth.currentFilterQ1;
	this.filter11.frequency.value = Math.pow(2, synth.currentFilterCutoff1); 
	
	this.filter12 = audioContext.createBiquadFilter();
	this.filter12.type = "lowpass";
	this.filter12.Q.value = synth.currentFilterQ1;
	this.filter12.frequency.value = Math.pow(2, synth.currentFilterCutoff1); 
	
	// create the LP filter2
	this.filter21 = audioContext.createBiquadFilter();
	this.filter21.type = "lowpass";
	this.filter21.Q.value = synth.currentFilterQ2;
	this.filter21.frequency.value = Math.pow(2, synth.currentFilterCutoff2); 
	
	this.filter22 = audioContext.createBiquadFilter();
	this.filter22.type = "lowpass";
	this.filter22.Q.value = synth.currentFilterQ2;
	this.filter22.frequency.value = Math.pow(2, synth.currentFilterCutoff2);

	this.osc1Gain.connect( this.filter11 );
	this.osc2Gain.connect( this.filter21 );
	
	this.filter11.connect( this.filter12 );
	this.filter21.connect( this.filter22 );

	// connect the modulator1 to the filters1
	this.modFilterGain1 = audioContext.createGain();
	this.modOsc1.connect( this.modFilterGain1 );
	this.modFilterGain1.gain.value = synth.currentFilterMod1*24;
	this.modFilterGain1.connect( this.filter11.detune );	// filter tremolo
	this.modFilterGain1.connect( this.filter12.detune );	// filter tremolo
	
	// connect the modulator2 to the filters1
	this.modFilterGain2 = audioContext.createGain();
	this.modOsc2.connect( this.modFilterGain2 );
	this.modFilterGain2.gain.value = synth.currentFilterMod2*24;
	this.modFilterGain2.connect( this.filter21.detune );	// filter tremolo
	this.modFilterGain2.connect( this.filter22.detune );	// filter tremolo

	// create the volume envelopes
	this.envelope1 = audioContext.createGain();
	this.envelope2 = audioContext.createGain();
	this.filter12.connect( this.envelope1 );
	this.filter22.connect( this.envelope2 );
	this.envelope1.connect( synth.effectChain );
	this.envelope2.connect( synth.effectChain );

	// set up the volume and filter envelopes
	var now = audioContext.currentTime;
	
	var envAttackEnd1 = now + durationSec*synth.currentEnvA1/100.0;
	this.envelope1.gain.value = 0.0;
	this.envelope1.gain.setValueAtTime( 0.0, now );//first set mute 
	this.envelope1.gain.exponentialRampToValueAtTime( 1.0, envAttackEnd1 );//liner increase to 1.0 at attack end
	this.envelope1.gain.setTargetAtTime( (synth.currentEnvS1/100.0), envAttackEnd1, (synth.currentEnvD1/100.0)+0.001 );// then make sustain
	
	var envAttackEnd2 = now + durationSec*synth.currentEnvA2/100.0;
	this.envelope2.gain.value = 0.0;
	this.envelope2.gain.setValueAtTime( 0.0, now );//first set mute 
	this.envelope2.gain.exponentialRampToValueAtTime( 1.0, envAttackEnd2 );//liner increase to 1.0 at attack end
	this.envelope2.gain.setTargetAtTime( (synth.currentEnvS2/100.0), envAttackEnd2, (synth.currentEnvD2/100.0)+0.001 );// then make sustain

	var filterAttackLevel = synth.currentFilterEnv*72;  // Range: 0-7200: 6-octave range
	
	var filterSustainLevel1 = filterAttackLevel* synth.currentFilterEnvS1 / 100.0; // range: 0-7200
	var filterSustainLevel2 = filterAttackLevel* synth.currentFilterEnvS2 / 100.0; // range: 0-7200
	
	var filterAttackEnd1 = (synth.currentFilterEnvA1/20.0);
	var filterAttackEnd2 = (synth.currentFilterEnvA2/20.0);
	
	if (!filterAttackEnd1) {
		filterAttackEnd1=0.05; // tweak to get target decay to work properly
	}
	if (!filterAttackEnd2) {
		filterAttackEnd2=0.05; // tweak to get target decay to work properly
	}
	//reset filters for osc1, detune
	this.filter11.detune.setValueAtTime( 0, now );
	this.filter12.detune.setValueAtTime( 0, now );
	
	this.filter11.detune.exponentialRampToValueAtTime( filterAttackLevel, now+filterAttackEnd1 );
	this.filter12.detune.exponentialRampToValueAtTime( filterAttackLevel, now+filterAttackEnd1 );
	
	this.filter11.detune.setTargetAtTime( filterSustainLevel1, now+filterAttackEnd1, (synth.currentFilterEnvD1/100.0) );
	this.filter12.detune.setTargetAtTime( filterSustainLevel1, now+filterAttackEnd1, (synth.currentFilterEnvD1/100.0) );

	//reset filters for osc1
	this.filter21.detune.setValueAtTime( 0, now );
	this.filter22.detune.setValueAtTime( 0, now );
	
	this.filter21.detune.exponentialRampToValueAtTime( filterAttackLevel, now+filterAttackEnd2 );
	this.filter22.detune.exponentialRampToValueAtTime( filterAttackLevel, now+filterAttackEnd2 );
	
	this.filter21.detune.setTargetAtTime( filterSustainLevel2, now+filterAttackEnd2, (synth.currentFilterEnvD2/100.0) );
	this.filter22.detune.setTargetAtTime( filterSustainLevel2, now+filterAttackEnd2, (synth.currentFilterEnvD2/100.0) );
	
	this.osc1.start(this._start);
	this.osc2.start(this._start);
	this.modOsc1.start(this._start);
	this.modOsc2.start(this._start);
	
	if(this._autostop){
		var tick =  this._stop;
		this.envelope1.gain.cancelScheduledValues(tick);
		this.envelope2.gain.cancelScheduledValues(tick);
		
		var release1 = tick + this._duration*this.synth.currentEnvR1/100.0;
		//this.envelope1.gain.setValueAtTime( this.envelope1.gain.value, tick );  // this is necessary because of the linear ramp
		this.envelope1.gain.setTargetAtTime(0.0, tick, release1);
		
		var release2 = tick + this._duration*this.synth.currentEnvR2/100.0;
		//this.envelope2.gain.setValueAtTime( this.envelope2.gain.value, tick );  // this is necessary because of the linear ramp
		this.envelope2.gain.setTargetAtTime(0.0, tick, release2);
		
		//change filters
		this.filter11.detune.cancelScheduledValues(tick);
		this.filter12.detune.setTargetAtTime( 0, tick, (this.synth.currentFilterEnvR1/100.0) );
		
		this.filter11.detune.setTargetAtTime( 0, tick, (this.synth.currentFilterEnvR1/100.0) );
		this.filter12.detune.cancelScheduledValues(tick);
		
		//change filters
		this.filter21.detune.cancelScheduledValues(tick);
		this.filter22.detune.setTargetAtTime( 0, tick, (this.synth.currentFilterEnvR2/100.0) );
		
		this.filter21.detune.setTargetAtTime( 0, tick, (this.synth.currentFilterEnvR2/100.0) );
		this.filter22.detune.cancelScheduledValues(tick);
		
		this.osc1.stop(release1);
		this.osc2.stop(release2);
		this.modOsc1.stop(release1);
		this.modOsc2.stop(release2);
	}
}
jweb.util.Voice.prototype= {
	setModWaveform1 : function( value ) {
		this.modOsc1.type = value;
	},
	setModWaveform2 : function( value ) {
		this.modOsc2.type = value;
	},
	updateModFrequency1 : function( value ) {
		this.modOsc1.frequency.value = value;
	},
	updateModFrequency2 : function( value ) {
		this.modOsc1.frequency.value = value;
	},
	updateModOsc1 : function( value ) {
		this.modOsc1Gain.gain.value = value/10;
	},

	updateModOsc2 : function( value ) {
		this.modOsc2Gain.gain.value = value/10;
	},

	setOsc1Waveform : function( value ) {
		this.osc1.type = value;
	},

	updateOsc1Frequency : function( value ) {
		this.osc1.frequency.value = (this.originalFrequency*Math.pow(2, this.synth.currentOsc1Octave+1));
		this.osc1.detune.value = this.synth.currentOsc1Detune + this.synth.currentPitchWheel * 500;	// value in cents - detune major fifth.
	},

	updateOsc1Mix : function( value ) {
		this.osc1Gain.gain.value = 0.005 * value;
	},

	setOsc2Waveform : function( value ) {
		this.osc2.type = value;
	},

	updateOsc2Frequency : function( value ) {
		this.osc2.frequency.value = (this.originalFrequency*Math.pow(2, this.synth.currentOsc2Octave));
		this.osc2.detune.value = this.synth.currentOsc2Detune + this.synth.currentPitchWheel * 500;	// value in cents - detune major fifth.
	},

	updateOsc2Mix : function( value ) {
		this.osc2Gain.gain.value = 0.005 * value;
	},

	setFilterCutoff1 : function( value ) {
		var now =  this.ctx.currentTime;
		var filterFrequency = Math.pow(2, value);
		window.PageBus.publish(constants.topicLog, {level:'DEBUG', cuteoff:filterFrequency});
		this.filter11.frequency.value = filterFrequency;
		this.filter12.frequency.value = filterFrequency;
	},
	setFilterCutoff2 : function( value ) {
		var now =  this.ctx.currentTime;
		var filterFrequency = Math.pow(2, value);
		window.PageBus.publish(constants.topicLog, {level:'DEBUG', cuteoff:filterFrequency});
		this.filter21.frequency.value = filterFrequency;
		this.filter22.frequency.value = filterFrequency;
	},

	setFilterQ1 : function( value ) {
		this.filter11.Q.value = value;
		this.filter12.Q.value = value;
	},
	setFilterQ2 : function( value ) {
		this.filter21.Q.value = value;
		this.filter22.Q.value = value;
	},
	setFilterMod1 : function( value ) {
		this.modFilterGain1.gain.value = this.synth.currentFilterMod1*24;
	},
	setFilterMod2 : function( value ) {
		this.modFilterGain2.gain.value = this.synth.currentFilterMod2*24;
	},

	noteOff : function() {
		var now =  this.ctx.currentTime;
		var release = now + (this.synth.currentEnvR/10.0);//real release time
		
		this.envelope.gain.cancelScheduledValues(now);
		this.envelope.gain.setValueAtTime( this.envelope.gain.value, now );  // this is necessary because of the linear ramp
		this.envelope.gain.setTargetAtTime(0.0, now, (this.synth.currentEnvR/100));
		
		// change filters for osc1
		this.filter1.detune.cancelScheduledValues(now);
		this.filter2.detune.setTargetAtTime( 0, now, (this.synth.currentFilterEnvR1/100.0) );
		
		this.filter1.detune.setTargetAtTime( 0, now, (this.synth.currentFilterEnvR1/100.0) );
		this.filter2.detune.cancelScheduledValues(now);
		
		// change filters for osc2
		this.filter21.detune.cancelScheduledValues(now);
		this.filter22.detune.setTargetAtTime( 0, now, (this.synth.currentFilterEnvR2/100.0) );
		
		this.filter21.detune.setTargetAtTime( 0, now, (this.synth.currentFilterEnvR2/100.0) );
		this.filter22.detune.cancelScheduledValues(now);
		
		this.osc1.stop( release );
		this.osc2.stop( release );
	}
}

jweb.util.Synthesizer = function() {
	this.subscriberId=null;
	this.mute = false;
  
	/*midi keyboard triggering voices*/
	this.voices = new Array();
	/*current working rhythm voice*/
	this.rhythmVoice = null;
	this.audioContext = null;
	this.isMobile = false;	// we have to disable the convolver on mobile for performance reasons.

	// This is the "initial patch"
	this.currentModWaveform1 = 0;	// SINE
	this.currentModWaveform2 = 0;	// SINE
	
	this.currentModFrequency1 = 2.1; // Hz * 10 = 2.1
	this.currentModFrequency2 = 2.1; // Hz * 10 = 2.1
	
	this.currentModOsc1 = 15;
	this.currentModOsc2 = 17;

	this.currentOsc1Waveform = 2; // SAW
	this.currentOsc1Octave = 0;  // 32'
	this.currentOsc1Detune = 0;	// 0
	this.currentOsc1Mix = 50.0;	// 50%

	this.currentOsc2Waveform = 2; // SAW
	this.currentOsc2Octave = 0;  // 16'
	this.currentOsc2Detune = -25;	// fat detune makes pretty analogue-y sound.  :)
	this.currentOsc2Mix = 50.0;	// 0%

	this.currentFilterCutoff1 = 8;
	this.currentFilterCutoff2 = 8;
	
	this.currentFilterQ1 = 7.0;
	this.currentFilterQ2 = 7.0;
	
	this.currentFilterMod1 = 21;
	this.currentFilterMod2 = 21;
	
	this.currentFilterEnv = 56;

	//ADSR for two osc
	this.currentEnvA1 = 2;
	this.currentEnvA2 = 2;
	
	this.currentEnvD1 = 15;
	this.currentEnvD2 = 15;
	
	this.currentEnvS1 = 68;
	this.currentEnvS2 = 68;
	
	this.currentEnvR1 = 5;
	this.currentEnvR2 = 5;

	this.currentFilterEnvA1 = 5;
	this.currentFilterEnvD1 = 6;
	this.currentFilterEnvS1 = 5;
	this.currentFilterEnvR1 = 7;
	
	this.currentFilterEnvA2 = 5;
	this.currentFilterEnvD2 = 6;
	this.currentFilterEnvS2 = 5;
	this.currentFilterEnvR2 = 7;

	this.currentDrive = 38;
	this.currentRev = 32;
	this.currentVol = 75;
	// end initial patch
	this.keys = new Array( 256 );
	
	this.effectChain = null;
	this.waveshaper = null;
	this.volNode = null;
	this.revNode = null;
	this.revGain = null;
	this.revBypassGain = null;
	this.compressor = null;
	this.masterDelay = null;
	this.masterAnalyser = null;
	
	this.currentPitchWheel = 0.0;
	
	this.currentOctave = 3;
	this.modOscFreqMultiplier = 1;
	this.moDouble = false;
	this.moQuadruple = false;
	this.waveforms = ["sine","square","sawtooth","triangle"];
	this.pointers=[];
	
	this.bindMidiKeys();
	this.start();
}
jweb.util.Synthesizer.prototype= {
	bindMidiKeys:function(){
		//Lower row: zsxdcvgbhnjm...
		this.keys[16] = 41; // = F2
		this.keys[65] = 42;
		this.keys[90] = 43;
		this.keys[83] = 44;
		this.keys[88] = 45;
		this.keys[68] = 46;
		this.keys[67] = 47;
		this.keys[86] = 48; // = C3
		this.keys[71] = 49;
		this.keys[66] = 50;
		this.keys[72] = 51;
		this.keys[78] = 52;
		this.keys[77] = 53; // = F3
		this.keys[75] = 54;
		this.keys[188] = 55;
		this.keys[76] = 56;
		this.keys[190] = 57;
		this.keys[186] = 58;
		this.keys[191] = 59;

		// Upper row: q2w3er5t6y7u...
		this.keys[81] = 60; // = C4 ("middle C")
		this.keys[50] = 61;
		this.keys[87] = 62;
		this.keys[51] = 63;
		this.keys[69] = 64;
		this.keys[82] = 65; // = F4
		this.keys[53] = 66;
		this.keys[84] = 67;
		this.keys[54] = 68;
		this.keys[89] = 69;
		this.keys[55] = 70;
		this.keys[85] = 71;
		this.keys[73] = 72; // = C5
		this.keys[57] = 73;
		this.keys[79] = 74;
		this.keys[48] = 75;
		this.keys[80] = 76;
		this.keys[219] = 77; // = F5
		this.keys[187] = 78;
		this.keys[221] = 79;
		this.keys[220] = 80;
	},
	
	onMessage:function(subj, msg, data) {
		if(subj == constants.contextInited){
			this.audioContext = msg.audioContext;//refer the obj
			window.PageBus.publish(constants.topicLog, {level:'DEBUG', content:'audioContext of analog synthesizer bind'});
			
			this.isMobile = (navigator.userAgent.indexOf("Android")!=-1)||(navigator.userAgent.indexOf("iPad")!=-1)||(navigator.userAgent.indexOf("iPhone")!=-1);
			
			// set up the master effects chain for all voices to connect to.
			this.effectChain = this.audioContext.createGain();
			this.waveshaper = new jweb.util.WaveShaper( this.audioContext );
			this.effectChain.connect( this.waveshaper.input );
			this.onUpdateDrive( this.currentDrive );

			if (!this.isMobile)
				this.revNode = this.audioContext.createConvolver();
			else
				this.revNode = this.audioContext.createGain();
			this.revGain = this.audioContext.createGain();
			this.revBypassGain = this.audioContext.createGain();

			this.volNode = this.audioContext.createGain();
			this.volNode.gain.value = this.currentVol;
			this.compressor = this.audioContext.createDynamicsCompressor();
			
			this.masterDelay = this.audioContext.createDelay();
			this.masterAnalyser = this.audioContext.createAnalyser();
			
			this.waveshaper.output.connect( this.revNode );
			this.waveshaper.output.connect( this.revBypassGain );
			this.revNode.connect( this.revGain );
			this.revGain.connect( this.volNode );
			this.revBypassGain.connect( this.volNode );
			this.onUpdateReverb( {currentTarget:{value:this.currentRev}} );

			this.volNode.connect( this.masterDelay );
			this.masterDelay.connect( this.compressor );
			
			this.compressor.connect(	this.audioContext.destination );
			this.compressor.connect(	this.masterAnalyser );
			this.onUpdateVolume( {currentTarget:{value:this.currentVol}} );

			var $this = this;
			if (!this.isMobile) {
				var irRRequest = new XMLHttpRequest();
				irRRequest.open("GET", "resources/sounds/longplate1.wav", true);
				irRRequest.responseType = "arraybuffer";
				irRRequest.onload = function() {
					$this.audioContext.decodeAudioData( irRRequest.response, 
						function(buffer) { 
							if ($this.revNode) $this.revNode.buffer = buffer; 
							else console.log("no revNode ready!")
						} );
				}
				irRRequest.send();
			}
			
			//fire data
			window.PageBus.publish(constants.synthNodeReady, {audioNode:this.waveshaper.output});
			window.PageBus.publish(constants.synth1Ready, {synth:$this});
			window.PageBus.publish('AnalyserReady', {analyser:$this.masterAnalyser});
		}
		else if(subj == constants.midiMessage){
			this.consumeMidiMessage(msg.event.data);//not consume message
		}		
	},
	consumeMidiMessage:function(dat){
		var cmd = dat[0] >> 4;
		var channel = dat[0] & 0xf;
		var noteNumber = dat[1];
		var velocity = dat[2];
		if ( cmd==8 || ((cmd==9)&&(velocity==0)) ) { // with MIDI, note on with velocity zero is the same as note off
			// note off
			this.noteOff( noteNumber );
		} 
		else if (cmd == 9) {
			// note on
			this.noteOn( noteNumber, velocity/127.0);
		} 
		else if (cmd == 11) {
			this.controller( noteNumber, velocity/127.0);
		} 
		else if (cmd == 14) {
			// pitch wheel
			this.pitchWheel( ((velocity * 128.0 + noteNumber)-8192)/8192.0 );
		} 
		else if ( cmd == 10 ) {  // poly aftertouch
			this.polyPressure(noteNumber,velocity/127);
		} 
		else {
			//console.log( "" + ev.data[0] + " " + ev.data[1] + " " + ev.data[2]);
		}
	},
	start:function() {
		var $this = this;
		if(this.subscriberId==null){
			this.subscriberId = window.PageBus.subscribe('**', this, this.onMessage, null);
		}
		else{
			this.subscriberId = window.PageBus.subscribe(this.subscriberId, this, this.onMessage, null);
		}
		$(document).keydown(function(event){
			$this.keyDown(event);
		});
		$(document).keyup(function(event){
			$this.keyUp(event);
		});
	},
	stop:function() {
		if(this.subscriberId!=null){
			window.PageBus.unsubscribe(this.subscriberId);
			this.subscriberId=null;
		}
	},
	scheduleNote:function(note, velocity, startTime, endTime){
		this.rhythmVoice = null; //dicard previous created one, Does this will let the reference weak and node get more chance to release??
		//a new one
		this.rhythmVoice = new jweb.util.Voice(note, velocity, this, true, startTime, endTime);
	},
	noteOn:function( note, velocity ) {
		if (this.voices[note] == null) {
			// Create a new synth node
		}
	},
	noteOff:function( note ) {
		if (this.voices[note] != null) {
			// Shut off the note playing and clear it 
			this.voices[note].noteOff();
			this.voices[note] = null;
		}
	},
	frequencyFromNoteNumber:function( note ) {
		return 440 * Math.pow(2,(note-69)/12);
	},
	controller:function( number, value ) {
		switch(number) {
		case 2:
			this.onUpdateFilterCutoff( 100*value );
			return;
		case 0x0a:
		case 7:
			this.onUpdateFilterQ( 20*value );
			return;
		case 1:
			this.onUpdateFilterMod(100*value);	
			return;
		case 0x49:
		case 5:
		case 15:
			this.onUpdateDrive( 100 * value );
			return;
		case 0x48:
		case 6:
		case 16:
			this.onUpdateReverb( 100 * value );
			return;
		case 0x4a:
			this.onUpdateModOsc1( 100 * value );
			return;
		case 0x47:
			this.onUpdateModOsc2( 100 * value );
			return;
		case 4:
		case 17:
			this.onUpdateModFrequency1( 10 * value );
			this.onUpdateModFrequency2( 10 * value );
			return;
		case 0x5b:
			this.onUpdateVolume( 100 * value );
			return;
		case 33: // "x1" button
		case 51:
			this.moDouble = (value > 0);
			this.changeModMultiplier();
			return;
		case 34: // "x2" button
		case 52:
			this.moQuadruple = (value > 0);
			this.changeModMultiplier();
			return;
		}
	},
	pitchWheel:function( value ) {
		var i;
		this.currentPitchWheel = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i]) {
				if (this.voices[i].osc1)
					this.voices[i].osc1.detune.value = this.currentOsc1Detune + this.currentPitchWheel * 500;	// value in cents - detune major fifth.
				if (this.voices[i].osc2)
					this.voices[i].osc2.detune.value = this.currentOsc2Detune + this.currentPitchWheel * 500;	// value in cents - detune major fifth.
			}
		}
		
		if(this.rhythmVoice!=null){
			if (this.rhythmVoice.osc1)
				this.rhythmVoice.osc1.detune.value = this.currentOsc1Detune + this.currentPitchWheel * 500;	// value in cents - detune major fifth.
			if (this.rhythmVoice.osc2)
				this.rhythmVoice.osc2.detune.value = this.currentOsc2Detune + this.currentPitchWheel * 500;	// value in cents - detune major fifth.
		}
	},
	polyPressure:function( noteNumber, value ) {
		if (this.voices[noteNumber] != null) {
			this.voices[noteNumber].setFilterQ( value*20 );
		}
		if(this.rhythmVoice!=null&&this.rhythmVoice.note==noteNumber){
			this.rhythmVoice.setFilterQ1( value*20 );
			this.rhythmVoice.setFilterQ2( value*20 );
		}
	},
	/**FM wave type*/
	onUpdateModWaveform1:function( ev ) {
		this.currentModWaveform1 = (ev.target?ev.target.selectedIndex : ev);
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setModWaveform1( this.waveforms[this.currentModWaveform1] );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setModWaveform2( this.waveforms[this.currentModWaveform1] );
		}
	},
	onUpdateModWaveform2:function( ev ) {
		this.currentModWaveform2 = (ev.target?ev.target.selectedIndex : ev);
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setModWaveform2( this.waveforms[this.currentModWaveform2] );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setModWaveform2( this.waveforms[this.currentModWaveform2] );
		}
	},

	onUpdateModFrequency1:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentModFrequency1 = value;
		var oscFreq = this.currentModFrequency1 * this.modOscFreqMultiplier;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateModFrequency1( oscFreq );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateModFrequency1( oscFreq );
		}
	},
	onUpdateModFrequency2:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentModFrequency2 = value;
		var oscFreq = this.currentModFrequency2 * this.modOscFreqMultiplier;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateModFrequency2( oscFreq );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateModFrequency2( oscFreq );
		}
	},

	onUpdateModOsc1:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentModOsc1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateModOsc1( this.currentModOsc1 );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateModOsc1( this.currentModOsc1 );
		}
	},

	onUpdateModOsc2:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentModOsc2 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateModOsc2( this.currentModOsc2 );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateModOsc2( this.currentModOsc2 );
		}
	},
	/*cuteoffs*/
	onUpdateFilterCutoff1:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterCutoff1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterCutoff1( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterCutoff1( value );
		}
	},
	onUpdateFilterCutoff2:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterCutoff1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterCutoff2( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterCutoff2( value );
		}
	},
	/*filters Q*/
	onUpdateFilterQ1:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterQ1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterQ1( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterQ1( value );
		}
	},
	onUpdateFilterQ2:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterQ1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterQ2( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterQ2( value );
		}
	},

	onUpdateFilterMod1:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterMod1 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterMod1( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterMod1( value );
		}
	},
	onUpdateFilterMod2:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterMod2 = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setFilterMod2( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setFilterMod2( value );
		}
	},
	onUpdateFilterEnv:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		this.currentFilterEnv = value;
	},

	onUpdateOsc1Wave:function( ev ) {
		this.currentOsc1Waveform = (ev.target?ev.target.selectedIndex:ev);
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setOsc1Waveform( this.waveforms[this.currentOsc1Waveform] );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setOsc1Waveform( this.waveforms[this.currentOsc1Waveform] );
		}
	},

	onUpdateOsc1Octave:function( ev ) {
		this.currentOsc1Octave = ev.target.selectedIndex;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc1Frequency();
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc1Frequency();
		}
	},

	onUpdateOsc1Detune:function( ev ) {
		var value = ev.currentTarget?ev.currentTarget.value:ev;
		this.currentOsc1Detune = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc1Frequency();
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc1Frequency();
		}
	},

	onUpdateOsc1Mix:function( value ) {
		if (value.currentTarget) value = value.currentTarget.value;
		this.currentOsc1Mix = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc1Mix( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc1Mix( value );
		}
	},

	onUpdateOsc2Wave:function( ev ) {
		this.currentOsc2Waveform = (ev.target?ev.target.selectedIndex:ev);
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].setOsc2Waveform( this.waveforms[this.currentOsc2Waveform] );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.setOsc2Waveform( this.waveforms[this.currentOsc2Waveform] );
		}
	},

	onUpdateOsc2Octave:function( ev ) {
		this.currentOsc2Octave = (ev.target?ev.target.selectedIndex:ev);
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc2Frequency();
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc2Frequency();
		}
	},

	onUpdateOsc2Detune:function( ev ) {
		var value = (ev.currentTarget?ev.currentTarget.value:ev);
		this.currentOsc2Detune = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc2Frequency();
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc2Frequency();
		}
	},

	onUpdateOsc2Mix:function( ev ) {
		var value = (ev.currentTarget?ev.currentTarget.value:ev);
		this.currentOsc2Mix = value;
		for (var i=0; i<255; i++) {
			if (this.voices[i] != null) {
				this.voices[i].updateOsc2Mix( value );
			}
		}
		if(this.rhythmVoice!=null){
			this.rhythmVoice.updateOsc2Mix( value );
		}
	},
	//ADSR
	onUpdateEnvA1:function( ev ) {
		this.currentEnvA1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateEnvA2:function( ev ) {
		this.currentEnvA2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	
	onUpdateEnvD1:function( ev ) {
		this.currentEnvD1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateEnvD2:function( ev ) {
		this.currentEnvD2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	
	onUpdateEnvS1:function( ev ) {
		this.currentEnvS1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateEnvS2:function( ev ) {
		this.currentEnvS2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},

	onUpdateEnvR1:function( ev ) {
		this.currentEnvR1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateEnvR2:function( ev ) {
		this.currentEnvR2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	
	/*osc filter attack env*/
	onUpdateFilterEnvA1:function( ev ) {
		this.currentFilterEnvA1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateFilterEnvA2:function( ev ) {
		this.currentFilterEnvA2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},

	onUpdateFilterEnvD:function( ev ) {
		this.currentFilterEnvD = (ev.currentTarget ?ev.currentTarget.value:ev);
	},

	onUpdateFilterEnvS:function( ev ) {
		this.currentFilterEnvS = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	/*osc filter release env*/
	onUpdateFilterEnvR1:function( ev ) {
		this.currentFilterEnvR1 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateFilterEnvR2:function( ev ) {
		this.currentFilterEnvR2 = (ev.currentTarget ?ev.currentTarget.value:ev);
	},
	onUpdateDrive:function( value ) {
		this.currentDrive = value;
		this.waveshaper.setDrive( 0.01 + (this.currentDrive*this.currentDrive/500.0) );
	},
	/*Master volume*/
	onUpdateVolume:function( ev ) {
		if(this.volNode) this.volNode.gain.value = (ev.currentTarget ? ev.currentTarget.value : ev)/100;
	},
	onUpdateReverb:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		value = value/100;

		// equal-power crossfade
		var gain1 = Math.cos(value * 0.5*Math.PI);
		var gain2 = Math.cos((1.0-value) * 0.5*Math.PI);

		if(this.revBypassGain)this.revBypassGain.gain.value = gain1;
		if(this.revGain)this.revGain.gain.value = gain2;
	},
	onUpdateDelay:function( ev ) {
		var value = ev.currentTarget ? ev.currentTarget.value : ev;
		if(this.masterDelay)this.masterDelay.delayTime.value = value;
	},
	filterFrequencyFromCutoff:function( pitch, cutoff ) {
		var nyquist = 0.5 * this.audioContext.sampleRate;
		var filterFrequency = Math.pow(2, (9 * cutoff) - 1) * pitch;
		if (filterFrequency > nyquist)
			filterFrequency = nyquist;
		return filterFrequency;
	},
	keyDown:function( ev ) {
		if ((ev.keyCode==49)||(ev.keyCode==50)) {
			if (ev.keyCode==49)
				this.moDouble = true;
			else if (ev.keyCode==50)
				this.moQuadruple = true;
			this.changeModMultiplier();
		}
		var note = this.keys[ev.keyCode];
		if (note)
			this.noteOn( note + 12*(3-this.currentOctave), 0.75 );
		//console.log( "key down: " + ev.keyCode );

		return false;
	},
	keyUp:function( ev ) {
		if ((ev.keyCode==49)||(ev.keyCode==50)) {
			if (ev.keyCode==49)
				this.moDouble = false;
			else if (ev.keyCode==50)
				this.moQuadruple = false;
			this.changeModMultiplier();
		}

		var note = this.keys[ev.keyCode];
		if (note)
			this.noteOff( note + 12*(3-this.currentOctave) );
		//	console.log( "key up: " + ev.keyCode );
		
		return false;
	},
	touchstart:function( ev ) {
		for (var i=0; i<ev.targetTouches.length; i++) {
			var touch = ev.targetTouches[0];
			var element = touch.target;

			var note = parseInt( element.id.substring( 1 ) );
			//console.log( "touchstart: id: " + element.id + "identifier: " + touch.identifier + " note:" + note );
			if (!isNaN(note)) {
				this.noteOn( note + 12*(3-currentOctave), 0.75 );
				this.pointers[touch.identifier]=note;
			}
		}
		ev.preventDefault();
	},
	touchmove:function( ev ) {
		for (var i=0; i<ev.targetTouches.length; i++) {
			var touch = ev.targetTouches[0];
			var element = touch.target;

			var note = parseInt( element.id.substring( 1 ) );
			//console.log( "touchmove: id: " + element.id + "identifier: " + touch.identifier + " note:" + note );
			if (!isNaN(note) && this.pointers[touch.identifier] && this.pointers[touch.identifier]!=note) {
				this.noteOff(pointers[touch.identifier] + 12*(3-this.currentOctave));
				this.noteOn( note + 12*(3-currentOctave), 0.75 );
				this.pointers[touch.identifier]=note;
			}
		}
		ev.preventDefault();
	},
	touchend:function( ev ) {
		var note = parseInt( ev.target.id.substring( 1 ) );
		//console.log( "touchend: id: " + ev.target.id + " note:" + note );
		if (note != NaN)
			this.noteOff( note + 12*(3-this.currentOctave) );
		this.pointers[ev.pointerId]=null;
		ev.preventDefault();
	},

	touchcancel:function( ev ) {
		//console.log( "touchcancel" );
		ev.preventDefault();
	},

	pointerDown:function( ev ) {
		var note = parseInt( ev.target.id.substring( 1 ) );
		/*
		if (pointerDebugging)
			console.log( "pointer down: id: " + ev.pointerId + " target: " + ev.target.id + " note:" + note );
		*/
		if (!isNaN(note)) {
			this.noteOn( note + 12*(3-this.currentOctave), 0.75 );
			this.pointers[ev.pointerId]=note;
		}
		ev.preventDefault();
	},
	pointerMove:function( ev ) {
		var note = parseInt( ev.target.id.substring( 1 ) );
		/*
		if (pointerDebugging)
			console.log( "pointer move: id: " + ev.pointerId + " target: " + ev.target.id + " note:" + note );
		*/
		if (!isNaN(note) && this.pointers[ev.pointerId] && this.pointers[ev.pointerId]!=note) {
			if (this.pointers[ev.pointerId])
				this.noteOff(this.pointers[ev.pointerId] + 12*(3-this.currentOctave));
			this.noteOn( note + 12*(3-currentOctave), 0.75 );
			this.pointers[ev.pointerId]=note;
		}
		ev.preventDefault();
	},
	pointerUp:function( ev ) {
		var note = parseInt( ev.target.id.substring( 1 ) );
		/*
		if (pointerDebugging)
			console.log( "pointer up: id: " + ev.pointerId + " note:" + note );
		*/
		if (note != NaN)
			this.noteOff( note + 12*(3-this.currentOctave) );
		this.pointers[ev.pointerId]=null;
		ev.preventDefault();
	},
	onChangeOctave:function( ev ) {
		this.currentOctave = ev.target.selectedIndex;
	},
	setMute:function(bol){
		this.mute = bol;
	}
}